package bme.andraskindler.treenventory.provider;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.Toast;
import bme.andraskindler.treenventory.TreenventoryApplication;
import bme.andraskindler.treenventory.activity.BluetoothActivity;
import bme.andraskindler.treenventory.imagemanipulation.Filters;
import bme.andraskindler.treenventory.imagemanipulation.Vector;

import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;

/**
 * A Bluetooth-kapcsolat létrehozásáért és fenntartásáért felelős osztály.
 * 
 * @author andraskindler
 * 
 */
public class BluetoothService {

	public static final int MESSAGE_STATE_CHANGE = 1;
	public static final int MESSAGE_READ = 2;
	public static final int MESSAGE_DEVICE_NAME = 4;

	public static final String DEVICE_NAME = "device_name";
	public static final String TOAST = "toast";

	private String mConnectedDeviceName = null;

	private BluetoothAdapter mAdapter;

	private static final String UUID = "cd255d02-ef16-4740-be83-462efd56bc0d";
	private static final String NAME = "ErdeszSecure";

	private final Handler mHandler;
	private AcceptThread mSecureAcceptThread;
	private ConnectThread mConnectThread;
	private ConnectedThread mConnectedThread;

	private int mState;
	public static final int STATE_NONE = 0;
	public static final int STATE_LISTEN = 1;
	public static final int STATE_CONNECTING = 2;
	public static final int STATE_CONNECTED = 3;

	@SuppressLint("HandlerLeak")
	public BluetoothService() {
		mAdapter = BluetoothAdapter.getDefaultAdapter();

		mHandler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				switch (msg.what) {
				case MESSAGE_STATE_CHANGE:
					switch (msg.arg1) {
					case BluetoothService.STATE_CONNECTED:
						System.out.println("connected " + mConnectedDeviceName);
						break;
					case BluetoothService.STATE_CONNECTING:
						System.out.println("connecting to " + mConnectedDeviceName);
						break;
					case BluetoothService.STATE_LISTEN:
						System.out.println("listening");
						break;
					case BluetoothService.STATE_NONE:
						System.out.println("not connected");
						break;
					}
					break;
				case MESSAGE_READ:
					byte[] readBuf = (byte[]) msg.obj;
					String readMessage = new String(readBuf, 0, msg.arg1);
					if (readMessage.equalsIgnoreCase("photoreq")) {
						CameraProvider.getInstance().takePhoto();
					} else {
						Toast.makeText(TreenventoryApplication.getInstance(), "received" + readMessage, Toast.LENGTH_SHORT).show();
						Iterable<String> result = Splitter.on(" ").split(readMessage);
						Vector b1end = new Vector(Double.parseDouble(Iterables.get(result, 0)), Double.parseDouble(Iterables.get(result, 1)), Double.parseDouble(Iterables.get(result, 2)));
						Vector b1start = new Vector(Double.parseDouble(Iterables.get(result, 3)), Double.parseDouble(Iterables.get(result, 4)), .0);
						Vector b2end = new Vector(Double.parseDouble(Iterables.get(result, 5)), Double.parseDouble(Iterables.get(result, 6)), Double.parseDouble(Iterables.get(result, 7)));
						Vector b2start = new Vector(Double.parseDouble(Iterables.get(result, 8)), Double.parseDouble(Iterables.get(result, 9)), .0);
						Filters.setData(b1start, b2start, b1end, b2end);
					}
					break;
				case MESSAGE_DEVICE_NAME:
					mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
					TreenventoryApplication.getInstance().setConnectedDeviceName(mConnectedDeviceName);
					Toast.makeText(TreenventoryApplication.getInstance(), "Connected to " + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
					break;
				}
			}
		};
		start();
	}

	/**
	 * Az aktuális állapotot állítja be.
	 * 
	 * @param state
	 */
	private synchronized void setState(int state) {
		mState = state;
		TreenventoryApplication.getInstance().setConnected(state == STATE_CONNECTED ? true : false);
		mHandler.obtainMessage(BluetoothActivity.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
	}

	/**
	 * Visszatér az aktuális állapottal
	 */
	public synchronized int getState() {
		return mState;
	}

	/**
	 * Elindít egy AcceptThread szálat, mely fogadja a kapcsolatokat.
	 */
	public synchronized void start() {
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		setState(STATE_LISTEN);

		if (mSecureAcceptThread == null) {
			mSecureAcceptThread = new AcceptThread(true);
			mSecureAcceptThread.start();
		}
	}

	/**
	 * Elindít egy ConnectThread szálat, mellyel kapcsolatot kezdeményez.
	 * 
	 * @param device
	 *            a célkészülék
	 * @param secure
	 *            Biztonság típusa: Secure (true) , Insecure (false)
	 */
	public synchronized void connect(BluetoothDevice device, boolean secure) {
		if (mState == STATE_CONNECTING) {
			if (mConnectThread != null) {
				mConnectThread.cancel();
				mConnectThread = null;
			}
		}
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}
		mConnectThread = new ConnectThread(device, secure);
		mConnectThread.start();
		setState(STATE_CONNECTING);
	}

	/**
	 * Elindítja a ConnectedThread példányt, ami a kapcsolat menedzselésére szolgál.
	 * 
	 * @param socket
	 *            A kapcsolódási BluetoothSocket.
	 * @param device
	 *            A csatlakoztatott eszköz.
	 */
	public synchronized void connected(BluetoothSocket socket, BluetoothDevice device, final String socketType) {
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}

		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		if (mSecureAcceptThread != null) {
			mSecureAcceptThread.cancel();
			mSecureAcceptThread = null;
		}
		mConnectedThread = new ConnectedThread(socket, socketType);
		mConnectedThread.start();

		Message msg = mHandler.obtainMessage(BluetoothActivity.MESSAGE_DEVICE_NAME);
		Bundle bundle = new Bundle();
		bundle.putString(BluetoothActivity.DEVICE_NAME, device.getName());
		msg.setData(bundle);
		mHandler.sendMessage(msg);

		setState(STATE_CONNECTED);
	}

	/**
	 * Az összes szál megállítása.
	 */
	public synchronized void stop() {
		System.out.println("stopping all threads");

		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}

		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		if (mSecureAcceptThread != null) {
			mSecureAcceptThread.cancel();
			mSecureAcceptThread = null;
		}
		setState(STATE_NONE);
	}

	/**
	 * Írás a ConnectedThread-re async módon.
	 * 
	 * @param out
	 */
	public void write(byte[] out) {
		ConnectedThread r;
		synchronized (this) {
			if (mState != STATE_CONNECTED)
				return;
			r = mConnectedThread;
		}
		r.write(out);
	}

	/**
	 * Fotó kérése.
	 */
	public void requestPhoto() {
		ConnectedThread r;
		synchronized (this) {
			if (mState != STATE_CONNECTED)
				return;
			r = mConnectedThread;
		}
		r.sendPhotoRequest();
	}

	/**
	 * A csatlakozás sikertelenségét jelzi az Activity felé.
	 */
	private void connectionFailed() {
		Message msg = mHandler.obtainMessage(BluetoothActivity.MESSAGE_TOAST);
		Bundle bundle = new Bundle();
		bundle.putString(BluetoothActivity.TOAST, "Unable to connect device");
		msg.setData(bundle);
		mHandler.sendMessage(msg);

		BluetoothService.this.start();
	}

	/**
	 * A kapcsolat elvesztését jelzi az Activity felé.
	 */
	private void connectionLost() {
		Message msg = mHandler.obtainMessage(BluetoothActivity.MESSAGE_TOAST);
		Bundle bundle = new Bundle();
		bundle.putString(BluetoothActivity.TOAST, "Device connection was lost");
		msg.setData(bundle);
		mHandler.sendMessage(msg);

		BluetoothService.this.start();
	}

	/**
	 * Ez a szál addig fut, amíg el nem sikerül kapcsolódni.
	 */
	private class AcceptThread extends Thread {
		private final BluetoothServerSocket mmServerSocket;
		private String mSocketType;

		public AcceptThread(boolean secure) {
			BluetoothServerSocket tmp = null;
			mSocketType = "Secure";

			try {
				tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, java.util.UUID.fromString(UUID));
			} catch (IOException e) {
				System.out.println("failed to create a listening server socket");
				e.printStackTrace();
			}
			mmServerSocket = tmp;
		}

		public void run() {
			setName("AcceptThread" + mSocketType);

			BluetoothSocket socket = null;

			while (mState != STATE_CONNECTED) {
				try {
					socket = mmServerSocket.accept();
				} catch (IOException e) {
					e.printStackTrace();
					break;
				}

				if (socket != null) {
					synchronized (BluetoothService.this) {
						switch (mState) {
						case STATE_LISTEN:
						case STATE_CONNECTING:
							connected(socket, socket.getRemoteDevice(), mSocketType);
							break;
						case STATE_NONE:
						case STATE_CONNECTED:
							try {
								socket.close();
							} catch (IOException e) {
								System.out.println("Could not close unwanted socket");
								e.printStackTrace();
							}
							break;
						}
					}
				}
			}

		}

		public void cancel() {
			try {
				mmServerSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Ez a szál addig fut, amíg a készülékeke megpróbálnak kapcsolódni egymáshoz.
	 */
	private class ConnectThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final BluetoothDevice mmDevice;
		private String mSocketType;

		public ConnectThread(BluetoothDevice device, boolean secure) {
			mmDevice = device;
			BluetoothSocket tmp = null;
			mSocketType = secure ? "Secure" : "Insecure";

			try {
				tmp = device.createRfcommSocketToServiceRecord(java.util.UUID.fromString(UUID));
			} catch (IOException e) {
				e.printStackTrace();
			}
			mmSocket = tmp;
		}

		public void run() {
			setName("ConnectThread" + mSocketType);

			mAdapter.cancelDiscovery();

			try {
				mmSocket.connect();
			} catch (IOException e) {
				try {
					mmSocket.close();
				} catch (IOException e2) {
					e.printStackTrace();
				}
				connectionFailed();
				return;
			}

			synchronized (BluetoothService.this) {
				mConnectThread = null;
			}

			connected(mmSocket, mmDevice, mSocketType);
		}

		public void cancel() {
			try {
				mmSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Ez a szál fut, amíg él a kapcsolat.
	 */
	private class ConnectedThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final InputStream mmInStream;
		private final OutputStream mmOutStream;

		public ConnectedThread(BluetoothSocket socket, String socketType) {
			mmSocket = socket;
			InputStream tmpIn = null;
			OutputStream tmpOut = null;

			try {
				tmpIn = socket.getInputStream();
				tmpOut = socket.getOutputStream();
			} catch (IOException e) {
				e.printStackTrace();
			}

			mmInStream = tmpIn;
			mmOutStream = tmpOut;
		}

		public void run() {
			byte[] buffer = new byte[1024];
			int bytes;

			while (true) {
				try {

					bytes = mmInStream.read(buffer);

					mHandler.obtainMessage(BluetoothActivity.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
				} catch (IOException e) {
					System.out.println("disconnected");
					connectionLost();
					BluetoothService.this.start();
					break;
				}
			}
		}

		/**
		 * A csatlakoztatott outstreamre ír
		 * 
		 * @param buffer
		 * 
		 */
		public void write(byte[] buffer) {
			try {
				mmOutStream.write(buffer);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		/**
		 * Fényképkérést küld.
		 */
		public void sendPhotoRequest() {
			String msg = "photoreq";
			try {
				mmOutStream.write(msg.getBytes());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		public void cancel() {
			try {
				mmSocket.close();
			} catch (IOException e) {
				System.out.println("failed to close connection");
				e.printStackTrace();
			}
		}
	}

}
